package com.example.registerstarter;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;
import com.example.registerapi.RegisterInfo;
import com.example.registerapi.RegisterInfoHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import org.springframework.web.client.RestTemplate;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;


/**
 * @author：kangxuan
 * @date：2024/9/30 10:06 AM
 * @desc:
 */
@Service
public class RegisterService {

    @Value("${register.server.url}")
    private String registerUrl;
    @Value("${server.port}")
    private int port;
    @Value("${spring.application.name}")
    private String applicationName;

    @Autowired
    private RestTemplate restTemplate;

    public void register(String name) {
        String url = String.format("%s?name=%s&port=%d", registerUrl + "/register", name, port);

        // 发送GET请求并返回结果
        restTemplate.getForObject(url, String.class);
    }

    public void unregister(String name) {
        String url = String.format("%s?name=%s&port=%d", registerUrl + "/logout", name, port);

        // 发送GET请求并返回结果
        restTemplate.getForObject(url, String.class);
    }

    /**
     * 调用次数
     */
    private int callCount = 0;

    /**
     * 服务降级配置模拟
     */
    public static Map<String,String> MOCK_REDUCE_RANK_MAP = new HashMap<>();
    static {
        MOCK_REDUCE_RANK_MAP.put("com.example.demoapi.product.ProductService#getList", "[{\"name\":\"mock\",\"price\":\"100\"}]");
    }

    public Object callMethod(String className, Method method, Object[] args) throws UnsupportedEncodingException {
        // 根据注册中心配置，获取服务地址
        RegisterInfo registerInfo = getRegisterInfo(className);

        // 判断该服务的可访问性，是否需要熔断
        if (registerInfo.getFailCount() > 100000){
            // 先查看该方法是否配置了mock
            if (MOCK_REDUCE_RANK_MAP.containsKey(className + "#" + method.getName())){
                // 取消次数，让其可再次尝试 ，这里规则应该复杂一些，不然降级效果就不大了
                registerInfo.resetFailCount();
                return JSON.parseObject(MOCK_REDUCE_RANK_MAP.get(className + "#" + method.getName()), method.getGenericReturnType());
            }
            throw new RuntimeException("服务不可用，直接熔断:" + className);
        }
        String host = registerInfo.getHost();

        String argsStreamStr = getArgsStreamStr(method, args);
        try {
            StopWatch traceWatch = new StopWatch();
            traceWatch.start();
            String url = String.format("%s?className=%s&methodName=%s&args=%s", host + "/serverCall", className, method.getName(), URLEncoder.encode(argsStreamStr, "UTF-8"));
            String forObject = sendRequest(url);
            Object o = JSON.parseObject(forObject, method.getGenericReturnType());
            traceWatch.stop();

            // 如果超时，设置失败次数
            long totalTimeMillis = traceWatch.getTotalTimeMillis();
            if (totalTimeMillis < 500){
                registerInfo.addFailCount(0);
            } else if (totalTimeMillis < 1000){
                registerInfo.addFailCount(1);
            } else if (totalTimeMillis < 2000) {
                registerInfo.addFailCount(3);
            }
            return o;
        }catch (Exception e){
            e.printStackTrace();
            // 设置失败次数
            registerInfo.addFailCount(10);
            if (MOCK_REDUCE_RANK_MAP.containsKey(className + "#" + method.getName())){
                return JSON.parseObject(MOCK_REDUCE_RANK_MAP.get(className + "#" + method.getName()), method.getGenericReturnType());
            }
            throw new RuntimeException("服务访问失败，直接熔断:" + className);
        }

    }

    /**
     * 在一批服务中随机选择一个服务，在该方法内处理负载均衡的问题
     * @param className
     * @return
     */
    private RegisterInfo getRegisterInfo(String className) {
        List<RegisterInfo> registerInfos = RegisterInfoHolder.getRegisterInfos(className);
        List<RegisterInfo> onlineServices =  registerInfos.stream().filter(registerInfo -> {
            return registerInfo.isOnline();
        }).toList();
        if (onlineServices == null || onlineServices.size() == 0) {
            String msg = "服务调用失败！无服务可用:" + className;
            System.out.println(msg);
            throw new RuntimeException(msg);
        }
        RegisterInfo registerInfo = onlineServices.get(callCount % onlineServices.size());
        if (callCount >= onlineServices.size()) {
            callCount = 0;
        }
        callCount ++;
        return registerInfo;
    }

    private String sendRequest(String url) {
        // 创建HttpHeaders对象用于设置请求头
        HttpHeaders headers = new HttpHeaders();
        String reqChainTag = RegisterInfoHolder.getReqChainTag();
        headers.add(RegisterInfoHolder.REQ_CHAIN_TAG_NAME, reqChainTag); // 添加请求头
        // 创建HttpEntity对象，可以携带请求头和body（如果有的话）
        HttpEntity<String> entity = new HttpEntity<>("body", headers);
        // 使用exchange方法发送GET请求
        StopWatch traceWatch = new StopWatch();
        traceWatch.start();
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
        traceWatch.stop();

        // 记录请求信息
        System.out.println("removeReqChainTag:" + RegisterInfoHolder.getReqChainTag() + ",发起请求" + ",耗时：" + traceWatch.getTotalTimeMillis() + "毫秒");
        // 移除请求信息
        RegisterInfoHolder.removeReqChainTag();
        // 返回响应体
        return response.getBody();
    }

    public static String getArgsStreamStr(Method method, Object[] args) {
        JSONObject obj = new JSONObject();
        if (args == null) {
            return obj.toJSONString();
        }
        Class<?>[] parameterTypes = method.getParameterTypes();
        for (int i = 0; i < args.length; i++) {
            JSONObject temp = new JSONObject();
            temp.put("className", parameterTypes[i].getName());
            temp.put("isNull", false);
            if (args[i] == null) {
                temp.put("value", "");
                temp.put("isNull", true);
            } else {
                temp.put("value", args[i]);
            }

            obj.put(i + "", temp);
        }
        return obj.toJSONString();
    }

    public void addObserver() {
        String url = String.format("%s?name=%s&port=%d", registerUrl + "/observer", applicationName, port);

        // 发送GET请求并返回结果
        String forObject = restTemplate.getForObject(url, String.class);
        List<RegisterInfo> registerInfos = JSONArray.parseArray(forObject, RegisterInfo.class);

        RegisterInfoHolder.addRegisterInfo(registerInfos);
    }
}
